#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <pthread.h>
#include <limits.h>
#include "heap.h"

extern errno;
static size_t page_size = 0;
int is_initialised = 0;
int get_counter = 0;//if we try to release when get_counter is 0 then error

pthread_mutex_t mutex;

int ma_init(size_t mem, off_t swap, const char* swap_path)
{

    if (is_initialised == 0) {
        data_t = (struct data*) malloc (sizeof (struct data));
    }

    if (data_t->has_initialised == 1) {
        errno = EALREADY;
        perror("can't initialise");
        return 0;
    }

    if (mem>swap) {
        errno=EBADR;
        perror("");
        return 0;
    }

    void* buf;
    size_t size = align_to_page_size(mem);
    page_size = sysconf(_SC_PAGESIZE);

    if (posix_memalign(&buf, page_size, size)!=0) {
        errno = ENOMEM;
        perror("can't allocate memory");
        return 0;
    }

    poison_mem(buf, size);

    int fd=open(swap_path, O_RDWR|O_CREAT,0666);
    if(fd == -1) {
        if (errno!=EACCES)
            errno=EFAULT;
        perror("");
        return 0;
    }
    if (ftruncate(fd, (off_t)swap) != 0) {
        errno = EFBIG;
        perror("too big file required");
        return 0;
    }

    is_initialised = 1;
    data_t->fd=fd;
    data_t->start = buf;
    data_t->last = data_t->start;
    data_t->has_initialised = 1;
    data_t->mem=size;
    data_t->off = 0;
    data_t->end = 0;
    data_t->swap = swap;
    data_t->id = 1;
    data_t->list.first = NULL;
    data_t->list.last = NULL;
    data_t->swap_list.first = NULL;
    data_t->swap_list.last = NULL;

    pthread_mutex_init(&mutex, NULL);
    return 1;
}


void ma_deinit()
{

    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return;
    }

    if (close(data_t->fd) == -1) {
        perror ("can't close op1");
        return;
    }

    free(data_t->start);
    free(data_t);

    pthread_mutex_destroy(&mutex);
    return;
}

int ma_free (size_t id)
{

    page_size = sysconf(_SC_PAGESIZE);
    node_t* node;
    int ret;

    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return 0;
    }

    if ((id >= UINT_MAX) || (id == 0)) {
        errno = EINVAL;
        perror ("id is not a valid number");
        return 0;
    }

    pthread_mutex_lock(&mutex);
    if (id >= data_t->id) {
        errno = EFAULT;
        perror ("was not allocated yet");
        pthread_mutex_unlock(&mutex);
        return 0;
    }

    void *cloc;
    struct mem_control_block *loc_mcb;
    cloc = data_t->start;

    while(cloc != data_t->last) {

        loc_mcb = (struct mem_control_block *)cloc;

        if(loc_mcb->id == id) {

            if (loc_mcb->is_available == 0) {

                if (loc_mcb->is_mapped == 1) {
                    printf ("free3\n");
                    errno = EBUSY;
                    perror("was not released");
                    pthread_mutex_unlock(&mutex);
                    return 0;
                }

                if (loc_mcb->is_mapped == 0) {
                    loc_mcb->is_available = 1;
                    printf ("free3.1: without release\n");
                    poison_mem(cloc + page_size, loc_mcb->size - page_size);
                    pthread_mutex_unlock(&mutex);
                    return 1;
                }
            }
        }
        cloc = cloc + loc_mcb->size;
    }

    node = findbyval (&data_t->list, id);

    if (node == NULL) {
        pthread_mutex_unlock(&mutex);
        return 1;
    }

    ret = addnode(&data_t->swap_list, node->id, node->size, node->off);
    delnode(&data_t->list, node);

    pthread_mutex_unlock(&mutex);
    return 1;
}


size_t ma_alloc(size_t sz)
{
    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return 0;
    }

    if (data_t->mem < sz) {
        errno = E2BIG;
        perror("too big chunk");
        return 0;
    }

    pthread_mutex_lock(&mutex);

    if (data_t->end == 1) {
        errno = EMLINK;
        perror("run out of valid ids");
        pthread_mutex_unlock(&mutex);
        return 0;
    }

    size_t size = align_to_page_size(sz);
    void *cloc;//current_location
    struct mem_control_block *loc_mcb;//current_location_mcb(memory_controll_block)
    void *memloc;//memory_location
    size = size + align_to_page_size(sizeof(struct mem_control_block));
    memloc = 0;
    cloc = data_t->start;

    while(cloc != data_t->last) {

        loc_mcb = (struct mem_control_block *)cloc;

        if(loc_mcb->is_available) {

            if(loc_mcb->size >= size) {
                loc_mcb->is_available = 0;
                memloc = cloc;
                break;
            }
        }
        cloc = cloc + loc_mcb->size;
    }

    if(! memloc) {
        memloc = data_t->last;
        if (data_t->last + size>data_t->start+data_t->mem) {
            errno = ENOMEM;
            perror("not enough memory in heap");
            pthread_mutex_unlock(&mutex);
            return 0;
        }
        data_t->last = data_t->last + size;
        loc_mcb = (struct mem_control_block*)memloc;
        loc_mcb->is_available = 0;
        loc_mcb->size = size;
        loc_mcb->is_mapped = 0;
        loc_mcb->memloc = memloc;
    }

    loc_mcb->id = data_t->id;

    if (data_t->id != (size_t)-1)
        data_t->id++;
    else
        data_t->end = 1;

    pthread_mutex_unlock(&mutex);
    return loc_mcb->id;
}


void* ma_get(size_t id)
{

    int kaban;
    page_size = sysconf(_SC_PAGESIZE);
    void* ptr;
    size_t size;
    node_t* node;

    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return NULL;
    }

    if ((id >= UINT_MAX) || (id == 0)) {
        errno = EINVAL;
        perror ("id is not a valid number");
        return NULL;
    }

    pthread_mutex_lock(&mutex);

    void *cloc;
    void *memloc;
    node_t* node1;
    struct mem_control_block *loc_mcb;
    int ret;
    cloc = data_t->start;

    while(cloc != data_t->last) {

        loc_mcb = (struct mem_control_block *)cloc;

        if ((loc_mcb->id == id) && (loc_mcb->is_available == 0)) {

            //get found sth in heap!
            //if we can't mmap on the last unmapped position of file than we find unoccupied position

            if (loc_mcb->is_mapped == 1) {
                pthread_mutex_unlock(&mutex);
                get_counter++;
                return loc_mcb->memloc;
            }

            if (data_t->off+loc_mcb->size>data_t->swap) {

                //and we add node to the middle of the swap file

                node1 = findbysize(&data_t->swap_list, loc_mcb->size);
                if (node1 == NULL) {
                    //errno = ...???
                    //perror("no space in file");//TODO
                    pthread_mutex_unlock(&mutex);
                    return NULL;
                }

                ptr = mmap(loc_mcb->memloc + page_size, loc_mcb->size - page_size,
                        PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED,
                        data_t->fd, node1->off);
                if (ptr == MAP_FAILED) {
                    errno = EBADF;
                    perror("problems with mapping the file");
                    pthread_mutex_unlock(&mutex);
                    return NULL;
                }
                loc_mcb->is_mapped = 1;

                ret = addnode(&data_t->list, node1->id, node1->size, node1->off);
                delnode(&data_t->swap_list, node1);
            }
            else {
                //and we add the node to the end of the swap file
                ptr = mmap(loc_mcb->memloc + page_size, loc_mcb->size - page_size,
                    PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED,
                    data_t->fd, data_t->off);
                if (ptr == MAP_FAILED) {
                    errno = EBADF;
                    perror("problems with mapping the file");
                    pthread_mutex_unlock(&mutex);
                    return NULL;
                }
                loc_mcb->is_mapped = 1;

                ret = addnode(&data_t->list, id, loc_mcb->size, data_t->off);
                data_t->off = data_t->off + loc_mcb->size;
            }
            get_counter++;
            pthread_mutex_unlock(&mutex);
            return ptr;
        }
        cloc = cloc + loc_mcb->size;
    }

    //if we reached this place, there is no chunk with such id in heap
    //but it may be in swap file

    node = findbyval (&data_t->list, id);
    if (node == NULL) {
        errno = EFAULT;
        perror("no memory chunk with such id");
        pthread_mutex_unlock(&mutex);
        return NULL;
    }

    size = node->size;
    cloc = data_t->start;

    while(cloc != data_t->last) {

        loc_mcb = (struct mem_control_block *)cloc;

        if ((loc_mcb->is_available) && (loc_mcb->size >= size)) {
            loc_mcb->is_available = 0;
            memloc = cloc;

            ptr = mmap(loc_mcb->memloc + page_size, loc_mcb->size - page_size,
                    PROT_EXEC | PROT_READ | PROT_WRITE, MAP_SHARED,
                    data_t->fd, node1->off);
            loc_mcb->is_mapped = 1;
            if (ptr == MAP_FAILED) {
                errno = EBADF;
                perror("problems with mapping the file");
                pthread_mutex_unlock(&mutex);
                return NULL;
            }

            get_counter++;
            pthread_mutex_unlock(&mutex);
            return ptr;
        }
        cloc = cloc + loc_mcb->size;
    }
    //if we are here there is no place in heap
    errno = ENOMEM;
    perror("not enough memory in heap");
    pthread_mutex_lock(&mutex);
    return NULL;
}


int ma_release(size_t id)
{

    if (is_initialised == 0) {
        errno = ECANCELED;
        perror("can't initialise");
        return 0;
    }

    if ((id >= UINT_MAX) || (id == 0)) {
        errno = EINVAL;
        perror ("id is not a valid number");
        return 0;
    }

    if (get_counter == 0) {
        printf ("the number of calls of ma_release if higher than the number of ma_get!\n");
        return 0;
    }

    pthread_mutex_lock(&mutex);
    void *cloc;
    struct mem_control_block *loc_mcb;
    cloc = data_t->start;
    page_size = sysconf(_SC_PAGESIZE);

    while(cloc != data_t->last) {

        loc_mcb = (struct mem_control_block *)cloc;

        if (loc_mcb->id == id) {

            if (loc_mcb->is_available == 1) {
                errno = EFAULT;
                perror ("this chunk is free");
                pthread_mutex_unlock(&mutex);
                return 0;
            }

            loc_mcb->is_available = 1;

            if (loc_mcb->is_mapped == 0) {
                printf ("release6\n");
                //release without munmap
                pthread_mutex_unlock(&mutex);
                get_counter--;
                return 1;
            }

            //if mapped
            //release with munmap

            int k = munmap(loc_mcb + page_size, loc_mcb->size - page_size);
            if (k == -1) {
                printf("%s\n", strerror(errno));
            }
            loc_mcb->is_mapped = 0;

            pthread_mutex_unlock(&mutex);
            get_counter--;
            return 1;
        }
        cloc = cloc + loc_mcb->size;
    }

    errno = EFAULT;
    pthread_mutex_unlock(&mutex);
    perror("no chunk with such id");
}
